package simmar.xcs;

import java.util.Random;
import java.util.List;
import java.util.Map;

import simmar.utilities.DeepCopy;
import simmar.utilities.Helpers;
import simmar.utilities.Logger;

public class XCSDiscovery implements XCSConfig
{
    protected static Random randomNumber = new Random(System.currentTimeMillis());
    
    /**
     * 
     * @param globalTimestamp
     * @param condition
     * @param actionSet
     * @param population
     */
    protected static void runGAComponent(
            int globalTimestamp, 
            int[] condition, 
            List<XCSClassifier> actionSet, 
            Map<String, XCSClassifier> population
            )
    {
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS RUN GA COMPONENT ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");
        
        int timestampSum = 0;
        int numerositySum = 0;

        for (int i = 0; i < actionSet.size(); i++)
        {
            int timestamp = actionSet.get(i).getTimestamp();
            int numerosity = actionSet.get(i).getNumerosity();
            timestampSum += timestamp * numerosity;
            numerositySum += numerosity;
        }

        if ((globalTimestamp - timestampSum / numerositySum) > GA_FIRING_INTERVAL) // Do GA step
        {
//            System.out.println("---------------------------------------------------------------------------------------------------------");
//            Logger.log("**** XCS RUN GA COMPONENT ****\n");
//            System.out.println("---------------------------------------------------------------------------------------------------------");
            
            for (int i = 0; i < actionSet.size(); i++)
                actionSet.get(i).setTimestamp(globalTimestamp);

            XCSClassifier parentA = selectOffspring(actionSet);
            XCSClassifier parentB = selectOffspring(actionSet);
            
//          XCSClassifier parentB = null;
//            // If there's more than one classifier in the set, make sure we get two different parents
//            if (actionSet.size() > 1)
//            {
//                while (parentB == null || parentB.getRule().toString().equals(parentA.getRule().toString()))
//                    parentB = selectOffspring(actionSet);                
//            }
//            else
//            {
//            parentB = selectOffspring(actionSet);
//            }

            XCSClassifier childA = (XCSClassifier) DeepCopy.copy(parentA);
            XCSClassifier childB = (XCSClassifier) DeepCopy.copy(parentB);
            
//            Logger.log("\nRules after deepcopy1:\n" + childA.toString() + "\n\n" + childB.toString() + "\n\n");
            
            childA.setNumerosity(1);
            childB.setNumerosity(1);
            childA.setExperience(0);
            childB.setExperience(0);            
            
//            Logger.log("\nRules after deepcopy2:\n" + childA.toString() + "\n\n" + childB.toString() + "\n\n");
            
            if (childA == null || childB == null)
            {
                Logger.logLine("ERROR: one or more children is null. Exit program.");
                System.exit(0);
            }                

            if (randomNumber.nextFloat() < CROSSOVER_PROBABILITY)
            {
//                Logger.logMethod();
//                Logger.log("\nRules before crossover:\n" + childA.toString() + "\n\n" + childB.toString() + "\n\n");
                applyCrossover(childA, childB);
//                Logger.log("\nRules after crossover:\n" + childA.toString() + "\n\n" + childB.toString() + "\n\n");                
                
                childA.setPrediction((parentA.getPrediction() + parentB.getPrediction()) / 2);
                childA.setAccuracy((parentA.getAccuracy() + parentB.getAccuracy()) / 2);
                childA.setFitness((parentA.getFitness() + parentB.getFitness()) / 2);
                childB.setPrediction(childA.getPrediction());
                childB.setAccuracy(childA.getAccuracy());
                childB.setFitness(childA.getFitness());
            }

            childA.setFitness(childA.getFitness() * 0.1f);
            childB.setFitness(childB.getFitness() * 0.1f);

            // Possibly mutate childA
            applyMutation(childA, condition);

            if (DO_GA_SUBSUMPTION)
            {
                if (XCSSubsumption.doesSubsume(parentA, childA))
                    parentA.incrementNumerosity();
                else if (XCSSubsumption.doesSubsume(parentB, childA))
                    parentB.incrementNumerosity();
                else
                    XCSEdit.insertClassifier(childA, population);
            } else
                XCSEdit.insertClassifier(childA, population);

            XCSEdit.deleteClassifier(population);

            // Possibly mutate childB
            applyMutation(childB, condition);

            if (DO_GA_SUBSUMPTION)
            {
                if (XCSSubsumption.doesSubsume(parentA, childB))
                    parentA.incrementNumerosity();
                else if (XCSSubsumption.doesSubsume(parentB, childB))
                    parentB.incrementNumerosity();
                else
                    XCSEdit.insertClassifier(childB, population);
            } else
                XCSEdit.insertClassifier(childB, population);

            XCSEdit.deleteClassifier(population);
        }
        
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS RUN GA COMPONENT DONE ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");
    }

    /**
     * 
     * @param actionSet
     * @return
     */
    protected static XCSClassifier selectOffspring(List<XCSClassifier> actionSet)
    {
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS SELECT OFFSPRING ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");
        
        XCSClassifier offspring = null;
        float fitnessSum = 0.0f;

        for (int i = 0; i < actionSet.size(); i++)
            fitnessSum += actionSet.get(i).getFitness();

        float threshold = randomNumber.nextFloat() * fitnessSum;
        
//        Logger.logLine("Fitness sum: " + fitnessSum);
//        Logger.logLine("Fitness threshold: " + threshold);
//        Logger.logLine("Action set size: " + actionSet.size());
        
        fitnessSum = 0.0f;
        
        for (int i = 0; i < actionSet.size(); i++)
        {
            fitnessSum += actionSet.get(i).getFitness();
//            Logger.logLine("Fitness sum check: " + fitnessSum);

            if (fitnessSum > threshold) // TODO: CHECK UP ON ZERO FITNESS
            {
                offspring = actionSet.get(i);
                break;
            }                
        }
        
        if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
            Logger.logLine("Offspring selected: " + offspring.toString());

        return offspring;
    }

    /**
     * 
     * @param classifierA
     * @param classifierB
     */
    protected static void applyCrossover(XCSClassifier classifierA, XCSClassifier classifierB)
    {
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS APPLY CROSSOVER ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");
        
        int crossoverPoint = 0;
        
        while (crossoverPoint != 0)
            crossoverPoint = randomNumber.nextInt(CONDITION_SIZE);
        
        if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
            Logger.logLine("Crossover point: " + crossoverPoint);
        
        for (int i = crossoverPoint; i < CONDITION_SIZE; i++)
        {
              int temp = classifierA.getRule().getConditionElement(i);
              classifierA.getRule().setConditionElement(i, classifierB.getRule().getConditionElement(i));
              classifierB.getRule().setConditionElement(i, temp);
        }
    }

    /**
     * 
     * @param classifier
     * @param condition
     */
    protected static void applyMutation(XCSClassifier classifier, int[] condition)
    {        
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS APPLY MUTATION ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");

        for (int i = 0; i < CONDITION_SIZE; i++)
        {
            if (randomNumber.nextFloat() < MUTATION_PROBABILITY)
            {
                if (classifier.getRule().getCondition()[i] == -1)
                    classifier.getRule().getCondition()[i] = condition[i];
                else
                    classifier.getRule().getCondition()[i] = -1;
                
                if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
                    Logger.logLine("Applied mutation to condition[" + i + "]");
            }
        }

        if (randomNumber.nextFloat() < MUTATION_PROBABILITY)
        {
            Action nextAction = Action.UNDEFINED;
            boolean differentActionFound = false;
            
            while (nextAction == Action.UNDEFINED || !differentActionFound)
            {
                nextAction = Helpers.intToAction((randomNumber.nextInt(MARIO_ACTIONS.length)));
                
                if (classifier.getRule().getAction() != nextAction)
                    differentActionFound = true;
            }

            classifier.getRule().setAction(nextAction);
            
            if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
                Logger.logLine("Applied mutation to action");
        }
        
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS APPLY MUTATION DONE ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");
    }
}